/**
 * This file is part of Lineage II Client Data Parser.
 * 
 * Lineage II Client Data Parser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Lineage II Client Data Parser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Lineage II Client Data Parser.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.l2emuproject.parser;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import net.l2emuproject.Launcher;
import net.l2emuproject.entity.Recipe;
import net.l2emuproject.io.FileManager;
import net.l2emuproject.io.SafelyPackedFile;
import net.l2emuproject.parser.base.DefaultParser;
import net.l2emuproject.redist.UnsupportedException;

import javolution.text.TextBuilder;

/**
 * @author Savormix
 *
 */
public class RecipeParser extends DefaultParser
{
	private static final String[]	NEEDED_FILES	=
													{ Launcher.FILES[25] };
	private static final String[]	RECIPE_ITEM		=
													{ "id", "recipeId", "name", "craftLevel", "type", "successRate" };
	private static final String[]	MAT_PROD		=
													{ "id", "count" };
	private static final String[]	STAT			=
													{ "name", "value" };

	private final List<Recipe>		recipes;

	/**
	 * Creates a parser that parses recipe data. Needed Files:
	 * <LI>Recipe-c</LI>
	 * @param spf File array
	 * @throws IllegalArgumentException Wrong file(s) 
	 */
	public RecipeParser(SafelyPackedFile... spf) throws IllegalArgumentException
	{
		super(spf);
		recipes = new ArrayList<Recipe>(1000);
	}

	/** Creates a parser that validates files */
	public RecipeParser()
	{
		super();
		recipes = null;
	}

	private final boolean parseRecipe(int entryCnt, SafelyPackedFile spf)
	{
		if (!isValidator())
			log.info("Reading " + entryCnt + " entries from " + spf.getFileName());
		String name;
		int id, rpID, level, pID, pCnt, mp, rate, matCnt, matXtra, matIDs[], matCount[];
		for (int i = 0; i < entryCnt; i++)
		{
			if (isValidator())
				return (!spf.readString().isEmpty() && spf.readInt() == 1 && spf.readInt() > 0);
			name = spf.readString();
			id = spf.readInt();
			rpID = spf.readInt();
			level = spf.readInt();
			pID = spf.readInt();
			pCnt = spf.readInt();
			mp = spf.readInt();
			rate = spf.readInt();
			matCnt = spf.readInt();
			matIDs = new int[matCnt];
			matCount = new int[matCnt];
			if (spf.getChronicleID() > 8)
				matXtra = spf.readInt();
			else
				matXtra = 0;
			//if (matXtra > 0)
			//	log.debug("Example recipe[" + rpID + "]: " + name + " = " + matXtra);
			for (int j = 0; j < matCnt; j++)
			{
				matIDs[j] = spf.readInt();
				matCount[j] = spf.readInt();
			}
			recipes.add(new Recipe(name, id, rpID, level, pID, pCnt, mp, rate, matIDs, matCount, matXtra));
		}
		log.info("Finished reading " + spf.getFileName());
		return false;
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#getNeededFiles()
	 */
	@Override
	public String[] getNeededFiles()
	{
		return NEEDED_FILES;
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#getParseType()
	 */
	@Override
	public String getParseType()
	{
		return "recipe";
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#readClientData()
	 */
	@Override
	public void readClientData() throws IllegalAccessException
	{
		if (isValidator())
			throw new IllegalAccessException(toString());
		parseRecipe(entries[0], spf[0]);
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#selfAdvancedAnalysis()
	 */
	@Override
	public void selfAdvancedAnalysis() throws IllegalAccessException
	{
		if (isValidator())
			throw new IllegalAccessException(toString());
		for (Recipe r : recipes)
		{
			if (ANALYZE_DETERMINISTIC)
				r.deterministicAnalysis();
			if (ANALYZE_EXPERIMENTAL)
				r.experimentalAnalysis();
		}
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#validate(com.l2cdp.io.SafelyPackedFile)
	 */
	@Override
	public boolean validate(SafelyPackedFile spf)
	{
		try
		{
			return parseRecipe(spf.readInt(), spf);
		}
		catch (Exception e)
		{
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#writeServerData(int)
	 */
	@Override
	public void writeServerData(int chronicle) throws IllegalAccessException
	{
		if (isValidator())
			throw new IllegalAccessException(toString());
		synchronized (finLock)
		{
			addThread();
		}
		new Thread(new AsyncWriterRSrv(chronicle), AsyncWriterRSrv.class.getName()).start();
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.base.IParser#writeSimpleData(int)
	 */
	@Override
	public void writeSimpleData(int chronicle) throws IllegalAccessException
	{
		if (isValidator())
			throw new IllegalAccessException(toString());
		synchronized (finLock)
		{
			addThread();
		}
		new Thread(new AsyncWriterRSim(chronicle), AsyncWriterRSim.class.getName()).start();
	}

	protected class AsyncWriterRSim implements Runnable
	{
		private final int	cID;

		protected AsyncWriterRSim(int cID)
		{
			this.cID = cID;
		}

		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run()
		{
			TextBuilder tb = TextBuilder.newInstance();
			try
			{
				Writer w = FileManager.getWriter(cID, "recipe.properties", true);
				addGenInfo(cID, tb, COMMENT_PROP);
				for (Recipe r : recipes)
				{
					tb.append(r.getId());
					tb.append(EQUAL);
					tb.append(r.getName());
					tb.append(SEMICOLON);
					tb.append(r.getRpID());
					tb.append(COMMA);
					tb.append(r.getCraftType());
					tb.append(COMMA);
					tb.append(r.getLevel());
					tb.append(SEMICOLON);
					tb.append(r.getProductID());
					tb.append(COMMA);
					tb.append(r.getProductCount());
					tb.append(SEMICOLON);
					tb.append(r.getMp());
					tb.append(SEMICOLON);
					tb.append(r.getSuccess());
					if (cID > 8)
					{
						tb.append(SEMICOLON);
						tb.append(r.getMatExtra());
						tb.append(COMMA);
						tb.append(r.getMasterwork());
					}
					tb.append(LINE_BREAK);
					tb.append(r.getId());
					tb.append("mats");
					tb.append(EQUAL);
					for (int i = 0; i < r.getMatID().length; i++)
					{
						tb.append(r.getMatID()[i]);
						tb.append(COMMA);
						tb.append(r.getMatCount()[i]);
						tb.append(SEMICOLON);
					}
					tb.delete(tb.length() - 1, tb.length());
					tb.append(LINE_BREAK);
				}
				addGenInfo(-1, tb, COMMENT_PROP);
				w.write(tb.toString());
				w.close();
			}
			catch (UnsupportedException e)
			{
				log.fatal("Unsupported chronicle, will not write data!", e);
			}
			catch (IOException e)
			{
				log.error("Cannot write recipe.properties!", e);
			}
			finally
			{
				TextBuilder.recycle(tb);
				synchronized (finLock)
				{
					remThread();
				}
			}
		}
	}

	protected class AsyncWriterRSrv implements Runnable
	{
		private final int	cID;

		protected AsyncWriterRSrv(int cID)
		{
			this.cID = cID;
		}

		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run()
		{
			TextBuilder tb = TextBuilder.newInstance();
			try
			{
				Writer w = FileManager.getWriter(cID, "recipes.xml", false);
				xmlDeclare(tb);
				xmlDeclareDoc(tb, "recipes");
				addGenInfo(cID, tb, COMMENT_XML);
				xmlDeclare(tb, "list", false, 0);
				for (Recipe r : recipes)
				{
					xmlDeclare(tb, "item", RECIPE_ITEM, new Object[]
					{ r.getId(), r.getRpID(), r.getName(), r.getLevel(), r.getCraftType(), r.getSuccess() }, false, 1);
					if (r.getMp() > 0)
						xmlDeclare(tb, "statUse", STAT, new Object[]
						{ "MP", r.getMp() }, true, 2);
					xmlDeclare(tb, "production", MAT_PROD, new Object[]
					{ r.getProductID(), r.getProductCount() }, true, 2);
					if (r.getMasterwork() > 0)
						xmlDeclare(tb, "productionRare", MAT_PROD, new Object[]
						{ r.getProductID(), r.getProductCount() }, true, 2);
					for (int i = 0; i < r.getMatID().length; i++)
					{
						xmlDeclare(tb, "ingredient", MAT_PROD, new Object[]
						{ r.getMatID()[i], r.getMatCount()[i] }, true, 2);
					}
					xmlDeclareFinish(tb, "item", 1);
				}
				xmlDeclareFinish(tb, "list", 0);
				addGenInfo(-1, tb, COMMENT_XML);
				w.write(tb.toString());
				w.close();
			}
			catch (UnsupportedException e)
			{
				log.fatal("Unsupported chronicle, will not write data!", e);
			}
			catch (IOException e)
			{
				log.error("Cannot write recipes.xml!", e);
			}
			finally
			{
				TextBuilder.recycle(tb);
				synchronized (finLock)
				{
					remThread();
				}
			}
		}
	}
}
